/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mainPackage;

import Memmory.Interpreter;
import Memmory.Memmory;
import Memmory.Word;
import gui.Console;
import java.io.BufferedReader;
import java.io.FileReader;


public class RealMachine {
    public static final int BLOCK_COUNT = 100;
    public static final int VM_BLOCK_COUNT = 16;
    public static final int BLOCK_SIZE = 16;
    public static final int SUPMEM_STARTS_AT = 80;
    
    //Komandos skaitliukas
    public byte[] IC = new byte[2];
    
    //Bendro naudojimo
    public Word A = new Word();
    public Word B = new Word();
    
    //Puslapiu lentele
    public Word PTR = new Word();
    
    //Loginis
    public byte L;
    
    //Darbo rezimas
    public byte MODE = 'V';
    
    //Taimerio registras
    public byte TIMER = 0;
    private byte tactsToInterupt = 10;
    
    //Pertraukimai
    public byte SI;
    public byte PI;
    public byte TI;
    public byte IOI;
    
    //Kanalu busenos
    public byte CH1;
    public byte CH2;
    public byte CH3;
    
    public Memmory MEMMORY;
    
    public VirtualMachine VM;
    
    private Console CONSOLE;
    
    private String osURI;
    
    public RealMachine(Console console, String osURI) throws Exception {
        CONSOLE = console;
        this.osURI = osURI;
        MEMMORY = new Memmory(BLOCK_COUNT, BLOCK_SIZE);
        
        Word GDvector = new Word(0, 0, 91, 0);
        Word PDvector = new Word(0, 0, 90, 0);
        Word HALTvector = new Word(0, 0, 92, 0);
        Word TIMERVector = new Word(0, 0, 93, 0);
        
        MEMMORY.putWord(GDvector, 99, 0);
        MEMMORY.putWord(PDvector, 99, 1);
        MEMMORY.putWord(HALTvector, 99, 2);
        MEMMORY.putWord(TIMERVector, 99, 3);
        this.loadOS(osURI);
    }
    
    public void createVirtualMachine(int id, String programUri) throws Exception  {
        //Issisaugom PTR
        Word backupPTR = PTR;
        
        //Laikinai pakeiciam PTR, kad loadProgram veiktu :D
        PTR = new Word(SUPMEM_STARTS_AT + id*2+1);
        MEMMORY.putWord(PTR, SUPMEM_STARTS_AT + id*2, 0);
        
        //Irasom IC
        MEMMORY.putWord(new Word(0, 0, VirtualMachine.DATA_SEG_SIZE / BLOCK_SIZE, VirtualMachine.DATA_SEG_SIZE % BLOCK_SIZE), SUPMEM_STARTS_AT + id*2, 1);
        
        //Uzpildom puslapiu lentele
        for (int i = 0; i < VM_BLOCK_COUNT; i++) {
            Word w = new Word(id * 16 + i);
            MEMMORY.putWord(w, SUPMEM_STARTS_AT + id*2 + 1, i);
        }
        
        VM = new VirtualMachine(this);
        VM.loadProgram(programUri);
        
        //Atstatom PTR
        PTR = backupPTR;
    }
    
    public void activateVirtualMachine(int id) {
        //Neaktyvuojam, jei dar yra pertraukimu
        if (SI + PI + TI + IOI == 0) {
            int stateBlock = SUPMEM_STARTS_AT + id*2;

            PTR = MEMMORY.getWord(stateBlock, 0);
            byte[] ICword = MEMMORY.getWord(stateBlock, 1).getBytes();
            System.arraycopy(ICword, 2, IC, 0, 2);

            A = MEMMORY.getWord(stateBlock, 2);
            B = MEMMORY.getWord(stateBlock, 3);
            L = MEMMORY.getWord(stateBlock, 4).getBytes()[3];
            MODE = 'V';
        } else {
           TEST(false); 
        }
    }
    
    private void TEST(boolean saveState) {
        if (SI + PI + TI + IOI != 0) {
            if (saveState) {
                saveState();
            }
            MODE = 'S';
            
            int interuptNumber = -1;
             if (PI == 1) {
                interuptNumber = 3; //Pazeista adresacija
            } else if (PI == 2) {
                interuptNumber = 4; //Komanda NONE
            } else if (TI == 1) {
                interuptNumber = 5; //Timeris
            } else if(SI == 1) {
                interuptNumber = 0; //GD
                CH1 = 1;
            } else if (SI == 2) {
                interuptNumber = 1; //PD
                CH2 = 1;
            } else if (SI == 3) {
                interuptNumber = 2; //HALT
            } else {
                switch (IOI) {
                    case 1: //pirmas kanalas
                        interuptNumber = 6;
                        break;
                    case 2:  //antras kanalas
                        interuptNumber = 7;
                        break;
                    case 3: //pirmas ir antras kanalas
                        interuptNumber = 8; 
                        break;
                    case 4:  //trecias kanalas
                        interuptNumber = 9;
                        break;
                    case 5: //pirmas ir trecias kanalas
                        interuptNumber = 10; 
                        break;
                    case 6: //antras ir trecias kanalas
                        interuptNumber = 11; 
                        break;
                }
            }
            
            byte[] ICword = MEMMORY.getWord(99, interuptNumber).getBytes();
            System.arraycopy(ICword, 2, IC, 0, 2);
        }
    }
    
    private void dropVirtualMachine(int id) {
        int stateBlock = SUPMEM_STARTS_AT + id*2;
        
        for (int i = 0; i < BLOCK_SIZE; i++) {
            MEMMORY.putWord(new Word(0), stateBlock, i);
            MEMMORY.putWord(new Word(0), stateBlock + 1, i);
        }
    }
    
    private void saveState() {
        int stateBlock = PTR.getNumber() - 1;
        MEMMORY.putWord(PTR, stateBlock, 0);
        MEMMORY.putWord(new Word(0, 0, IC[0], IC[1]), stateBlock, 1);
        MEMMORY.putWord(A, stateBlock, 2);
        MEMMORY.putWord(B, stateBlock, 3);
        MEMMORY.putWord(new Word(0, 0, 0, L), stateBlock, 4);
    }
    
    public void performStep() {
        if (MODE == 'V') {
            VM.performStep();
            
            TIMER ++;
            if (TIMER >= tactsToInterupt) {
                TI = 1;
            }
            TEST(true);
        } else if (MODE == 'S') {
            supervisorStep();
        }
    }
    
    public void supervisorStep() {
        Word word = MEMMORY.getWord(IC[0], IC[1]);
        String cmd = Interpreter.fromWord(word);
        byte[] value = new byte[4];
        value = word.getBytes();

        switch (cmd) {
            case "TOASCII":
                this.TOASCII();
                break;
            case "ADD":
                this.ADD();
                break;
            case "SUB":
                this.SUB();
                break;
            case "MUL":
                this.MUL();
                break;
            case "DIV":
                this.DIV();
                break;
            case "CMP":
                this.CMP();
                break;
            case "MOVAB":
                this.MOVAB();
                break;
            case "MOVBA":
                this.MOVBA();
                break;
            case "LOAD":
                this.LOAD(value[2], value[3]);
                break;
            case "SET":
                this.SET(value[2], value[3]);
                break;
            case "JMP":
                this.JMP(value[2], value[3]);
                break;
            case "JMPE":
                this.JMPE(value[2], value[3]);
                break;
            case "ACTIVATEVM":
                this.activateVirtualMachine((PTR.getNumber() - 80)/2);
                return;
            case "DROPVM":
                this.dropVirtualMachine((PTR.getNumber() - 80)/2);
                break;
            case "CLEARSI":
                SI = 0;
                break;
            case "CLEARTI":
                TI = 0;
                break;
            case "CLEARPI":
                PI = 0;
                break;
            case "CLEARIOI":
                IOI = 0;
                break;
            case "CLEARTIMER":
                TIMER = 0;
                break;
            case "EXCHANGE":
                this.EXCHANGE();
                break;
            default:
                System.out.println("BLUE SCREEN!");
        }

        setIC(getIC() + 1);
    }
    
    public void setIC(int integer16) {
        IC[0] = (byte) (integer16 / BLOCK_SIZE);
        IC[1] = (byte) (integer16 % BLOCK_SIZE);
    }
    
    public int getIC() {
        return IC[0] * BLOCK_SIZE + IC[1];
    }
    
    public void loadOS(String fileUri) throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader(fileUri));
        
        String line;
        boolean isData = true;
        while ((line = reader.readLine()) != null) {
            if (line.startsWith("//")) {
                continue;
            }
            
            if (line.equals("")) {
                continue;
            }
            
            if (line.equals("CODE:")) {
                isData = false;
                continue;
            }
            
            if (line.equals("MEMMORY:")) {
                isData = true;
                continue;
            }
            
            String[] parts = line.split("<<");
            String[] addressParts = parts[0].split(",");
            
            if (isData) {
                Word word;
                if (parts[1].startsWith("'") && parts[1].endsWith("'")) { //tikrinam ar tarp kabuciu ' '
                    String noQuotes = parts[1].replace("'", "");
                    word = new Word(noQuotes.getBytes());
                }  else {
                    String[] wordParts = parts[1].split(",");
                    if (wordParts.length == 4) {
                        word = new Word(Integer.parseInt(wordParts[0]), Integer.parseInt(wordParts[1]), Integer.parseInt(wordParts[2]), Integer.parseInt(wordParts[3]));
                    } else {
                        word = new Word(Integer.parseInt(parts[1])); //kitu atveju skaicius
                    }
                }
                MEMMORY.putWord(word, Integer.parseInt(addressParts[0]), Integer.parseInt(addressParts[1]));
            } else {
                Word word = Interpreter.fromString(parts[1]);
                MEMMORY.putWord(word, Integer.parseInt(addressParts[0]), Integer.parseInt(addressParts[1]));
            }
        }
    }
    
    private void EXCHANGE() {
        if (CH1 == 1) {
            try {
                byte[] destAddr = MEMMORY.getWord(98, 2).getBytes();
                int destPointer = destAddr[2] * RealMachine.BLOCK_SIZE + destAddr[3];
                
                byte[] inputBytes = CONSOLE.getBytesFromConsole();
                for (int i = 0; i < inputBytes.length; i += 4) {
                    MEMMORY.putWord(new Word(inputBytes[i+0], inputBytes[i+1], inputBytes[i+2], inputBytes[i+3]), destPointer);
                    destPointer++;
                }
                CH1 = 0;
            } catch (Exception ex) {
            }
        }
        
        if (CH2 == 1) {
            byte[] srcAddr = MEMMORY.getWord(98, 1).getBytes();
            
            int srcPointer = srcAddr[2] * RealMachine.BLOCK_SIZE + srcAddr[3];
            boolean stop = false;
            for (int i = 0; i < RealMachine.BLOCK_SIZE; i++) {
                byte[] wordBytes = MEMMORY.getWord(srcPointer).getBytes();
                if (wordBytes[0] == 'n' && wordBytes[1] == 'n' && wordBytes[2] == 'n' && wordBytes[3] == 'n') {
                    this.CONSOLE.putTextToConsole("\n");
                } else {
                    for (int j = 0; j < 4; j++) {
                        if((char)wordBytes[j] == '$') {
                            stop = true;
                            break;
                        }
                        this.CONSOLE.putTextToConsole(Character.toString((char)wordBytes[j]));
                    }
                }
                if (stop) {
                    break;
                }
                srcPointer++;
            }
            CH2 = 0;
        }
    }
    
    public void ADD() {
        A.setValue(A.getNumber() + B.getNumber());
    }
    
    public void SUB() {
        A.setValue(A.getNumber() - B.getNumber());
    }
    
    public void MUL() {
        A.setValue(A.getNumber() * B.getNumber());
    }
    
    public void TOASCII() {
        int number = A.getNumber();
        
        String s = Integer.toString(number);
        
        byte[] asciiBytes = s.getBytes();
        

        byte[] forBytes = new byte[4];
        
        int copyLenght = 0;
        if (asciiBytes.length > 4) {
            copyLenght = 4;
        } else {
            copyLenght = asciiBytes.length;
        }
        System.arraycopy(asciiBytes, 0, forBytes, 0, copyLenght);

        A = new Word(forBytes);
    }
    
    public void DIV() {
        int valueA = A.getNumber();
        int valueB = B.getNumber();
        
        A.setValue(valueA / valueB);
        B.setValue(valueA % valueB);
    }
    
    public void CMP() {
        int cmpResult = A.getNumber() - B.getNumber();
        
        if (cmpResult < 0) {
            L = -1;
        } else if (cmpResult == 0) {
            L = 0;
        } else {
            L = 1;
        }
    }
    
    public void MOVAB() {
        A.setValue(B.getNumber());
    }
    
    public void MOVBA() {
        B.setValue(A.getNumber());
    }
    
    private void LOAD(byte x, byte y) {
        A.setValue(MEMMORY.getWord(x, y).getBytes());
    }
    
    private void SET(byte x, byte y) {
        MEMMORY.putWord(new Word(A.getNumber()), x, y);
    }
    
    private void JMP(byte x, byte y) {
        if (x == 0) {
            this.setIC(this.getIC() - (y + 1));
        } else {
            this.setIC(this.getIC() + y);
        }
    }
    
    private void JMPE(byte x, byte y) {
        if (this.L == 0) {
            if (x == 0) {
                this.setIC(this.getIC() - (y + 1));
            } else {
                this.setIC(this.getIC() + y);
            }
        }
    }
    
    public void restart() throws Exception {
        IC = new byte[2];
        A = new Word();
        B = new Word();
        PTR = new Word();
        L = 0;
        MODE = 'V';
        TIMER = 0;
        SI = 0;
        PI = 0;
        TI = 0;
        IOI = 0;
        CH1 = 0;
        CH2 = 0;
        CH3 = 0;
        this.CONSOLE.clearConsole();
        this.loadOS(this.osURI);
    }
}